#ifndef PHONEHANDLER_H
#define PHONEHANDLER_H

#include <QList>
#include <QMutex>
#include <QMutexLocker>
#include <QObject>
#include <QThread>
//#include <QWaitCondition>
#include <libusb.h>

#include "nvstuff.h"
#include "tools.h"
#include "usbtransfers.h"

// the commands sent from this class get interpreted by a lge kernel diag module
//! the 'full' command list is in /kernel/arch/arm/mach-msm/include/mach/lge_diagcmd.h
//! not all the commands listed in the header are implemented in the kernel module or in this driver (no peek/poke :( )
//! key codes for the key press function are in lge_keypress_diag.h

class MemRegion
{
public:
    enum
    {
        // the LG Optimus Elite only returns regions with type 1 and 2
        Other           = 0,
        Recommended     = 1,
        Mandatory       = 2,

        // minimum amount of data needed to load all the necessary stuff
        MinBuffSize     = 1 + 4 + 4 + 2 + 2
    };

    MemRegion(){}

    u8 type;
    QString desc;
    QString fileName;
    u32 address;
    u32 size;

    bool Load( const u8* stuff, u32 stuffSize, u32 &outSize )
    {
        if( stuffSize < MinBuffSize )
        {
            return false;
        }
        type = stuff[ 0 ];
        if( type > Mandatory )
        {
            type = Other;
        }

        address = qFromBigEndian( *(u32*)( stuff + 1 ) );
        size = qFromBigEndian( *(u32*)( stuff + 5 ) );

        desc = QString( (const char*)stuff + 9 );
        outSize = desc.size() + 1;

        fileName = QString( (const char*)stuff + 9 + outSize );
        outSize += fileName.size() + 1 + 1 + 4 + 4;

        if( outSize > stuffSize )
        {
            WRN << "Error parsing data";
            return false;
        }

        return true;
    }
};

// this class is a wrapper for the libusb interface.
// all calls are blocking
class PhoneHandler : public QObject
{
    Q_OBJECT
public:
    enum DeviceMode
    {
        NoDevice,       // no phone connected
        DL_Mode,        // phone connected in download mode
        Diag_Mode       // phone connected in Diag mode
    };

    enum ErrorCode
    {
        ErrorNone,          // nothing bad happened at all

    };

    enum
    {
        e2Timeout = 500,    // timeouts for certain usb requests
        e3Timeout = 1000,

        E2_BulkOut = LIBUSB_ENDPOINT_OUT | LIBUSB_TRANSFER_TYPE_BULK | LIBUSB_RECIPIENT_ENDPOINT,     // most of the commands sent to e2
        E3_BulkIn = LIBUSB_ENDPOINT_IN | LIBUSB_TRANSFER_TYPE_BULK | LIBUSB_RECIPIENT_OTHER,     // most of the commands sent to e3

        // the largest chunk of memory allowed to read at once
        //! the phone may support larger (i haven't tried), but we are using a fixed-size buffer to do the reads
        MaxMemChunkSize = 0xff0
    };

    explicit PhoneHandler( QObject *parent = 0 );
    ~PhoneHandler();

    DeviceMode FindDevice();
    void Close();
    DeviceMode Mode() const { return mode; }

    // returns true if the phone responds that it is DL mode
    bool PollDownloadMode();

    // returns true if the phone is in diag mode
    bool PollDiagMode();

    // switch the phone from Diag to Download mode
    bool RebootToDownloadMode();

    // switch from download normal (or diag) mode
    bool RebootPhone();

    // get the list of available memory regions (must be called while phone is in download mode)
    bool GetMemoryRegions( QList< MemRegion > &regions );

    // get a chunk if RAM
    //! this needs to be called while in download mode
    //! this function doesn't check the address.  the length should be MaxMemChunkSize or smaller
    //! i have successfully dumped the full memory by starting at the beginning of each region and
    //! reading sections at each 0xff0 increment, and i have successfully dumped an arbatrary range
    //! returns an empty QByteArray on error
    QByteArray DumpMemory( u32 addr, u32 len = MaxMemChunkSize );

    // get ESN.  this needs to be called from Diag mode
    //! returns 0 on error
    u32 GetEsn();

    // needs to be called from DL mode
    //QString BaseBandVersion();

    // identify NV
    bool NVIdentify();

    // read an NV item
    //! probably needs to be called from Diag mode
    //! returns empty array on error
    QByteArray ReadNVItem( u16 idx );

    // read specific NV items and parse the data from them
    bool ReadNV_MEID( u64 &out );
    bool ReadNV_ESN( u32 &out );
    bool ReadNV_BlueTooth( u64 &out );
    QString ReadNV_MSL();
    QString ReadNV_MDN();
    QString ReadNV_OTSL();
    QString ReadNV_UnlockCode();

    // send key presses
    //! numbers are sent as their ascii value - 0x31 sends '1'
    //! 0x09 opens search, but also sends '8' ? (PF1 button)
    //! 0x0a opens search, but also sends '9' ? (PF2 button)
    //! 0x0b opens search, but also sends '0' ? (msg button)
    //! 0x50 ('P') is home
    //! 0x51 ('Q') opens the menu, but it is called "End"
    //! 0x52 ('R') is "Clear"
    bool SendKeyPress( u16 key );

    // gets a buffer containing the framebuffer
    //! the kernel converts it to rgb565 before sending it
    //! it is sent in 150 small packets, so it might be a bit too slow to try and render 20 fps
    QByteArray GetLCD();

protected:
    DeviceMode mode;

    // buffers and return codes for talking to the interfaces
    u32 dBuff[ 4096 / sizeof( u32 ) ];
    u32 oBuff32[ 4096 / sizeof( u32 ) ];
    int out;
    int out2;

    // some sort of thread-safetyness
    QMutex *mutex;

    struct libusb_device_handle *devh;

    bool ParseMemoryRegions( const u8* buf, u32 len, QList< MemRegion > &regions );

    static void DoFcsInRequest( InRequest* ir )
    {
        DoFcs16( ((u8*)ir) + 1, sizeof( InRequest ) - 4 );
    }
    
signals:
    void Error( ErrorCode e, const QString &message );
    
public slots:
    
};

// thread for dumping large chunks of memory
class MemDumpThread : public QThread
{
    Q_OBJECT

public:
    MemDumpThread( PhoneHandler* phone, QObject *parent = 0 );
    ~MemDumpThread();

    // dumps a region of memory from the phone and writes it to path
    //! will return false if the thread is already doing something
    bool DumpRegion( u32 addr, u32 len, const QString &path );

    void CancelDump();

protected:
    PhoneHandler *ph;
    virtual void run();

signals:
    //void SendText( const QString & ); // not used here, but left in for copy/paste thread creating later
    void SendProgress( int );
    void SendDone( quint32 address, quint32 size, const QString &path );
    void SendFatalError( const QString & );



private:
    QMutex mutex;
    //QWaitCondition condition;

    bool abort;

    u32 address;
    u32 size;
    QString fName;

    void FatalError( const QString &str );

};

#endif // PHONEHANDLER_H
